package com.uxsino.simo.indicator.expression;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.activemq.thread.TaskRunner;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.uxsino.simo.indicator.CompoundIndicator;
import com.uxsino.simo.indicator.ExprField;
import com.uxsino.simo.indicator.ExprIndicator;
import com.uxsino.simo.indicator.IExprItem;
import com.uxsino.simo.indicator.Indicator;
import com.uxsino.simo.indicator.ListIndicator;
import com.uxsino.simo.indicator.NEIndicatorDepo;
import com.uxsino.simo.indicator.expression.mvel.ExprEvaluatorMVEL;

/**
 * evaluate expr indicator/field, other epxressions
 * it is suppose to be used by single thread.
 * 
 *
 */
public abstract class ExprEvaluator {
    private static Logger logger = LoggerFactory.getLogger(ExprEvaluator.class);

    protected NEIndicatorDepo depo;

    public ExprEvaluator() {
    }

    /**
     * evaluate an expression indicator
     * all referred indicator should be ready before evaluating
     * see {@link TaskRunner} {@link ExprIndicator}
     * @param indicator
     * @return
     */
    public Object evalExprIndicator(ExprIndicator indicator) {

        if (indicator.isDisabled() || indicator.getCompiled() == null) {
            logger.error("evaluate formula of {} is disabled", indicator.name);
            return null;
        }
        Object r = evalCompiledExpr(null, indicator.getCompiled());
        return r;
    }

    public void evalListFields(ListIndicator indicator, List<Map<String, Object>> lv) {

        if (lv != null) {
            for (int i = 0; i < lv.size(); i++) {
                Map<String, Object> row = lv.get(i);
                String indexCol = "$index$";
                row.put(indexCol, i);
                try {
                    evalCompoundFields(indicator, row);
                }catch (Exception e){
                    logger.warn("mvel expression error: {}", e);
                }finally {
                    row.remove(indexCol);
                }
            }
        }

    }

    public void filterList(String filterExpr, List<Map<String, Object>> lv) {

        if (StringUtils.isEmpty(filterExpr) || lv == null) {
            return;
        }

        Iterator<Map<String, Object>> it = lv.iterator();
        while (it.hasNext()) {
            Map<String, Object> v = it.next();
            try {
                Object r = evalExpr(v, filterExpr);

                if (r != null && Boolean.class.isAssignableFrom(r.getClass()) && ((boolean) r) == false) {
                    it.remove();
                }
            } catch (Exception e) {
                logger.error("error applying filter to indicator {}", e);
            }
        }

    }

    // public void evalFieldsInDepo() {
    // // Iterator<EntityInfo> it_depo = nsDepo.getNEIterator();
    // List<String> networkEntityIds = nsDepo.getNetworkEntityIds();
    //
    // for (String networkEntityId : networkEntityIds) {
    // NEIndicatorDepo neDepo = nsDepo.getNEIndicatorDepo(networkEntityId);
    // Iterator<Indicator> it_ind = neDepo.getIndicatorIterator();
    // while (it_ind.hasNext()) {
    // Indicator indicator = it_ind.next();
    // if (indicator instanceof ListIndicator) {
    // IndicatorValue value = neDepo.getIndicatorValue(indicator);
    // @SuppressWarnings("unchecked")
    // List<Map<String, Object>> lv = (List<Map<String, Object>>) value.value;
    // evalListFields(neDepo.entity, (ListIndicator) indicator, lv);
    // } else if (indicator instanceof CompoundIndicator) {
    // IndicatorValue value = neDepo.getIndicatorValue(indicator);
    // if (value == null)
    // continue;
    // @SuppressWarnings("unchecked")
    // Map<String, Object> v = (Map<String, Object>) (value.value);
    // evalCompoundFields(neDepo.entity, (CompoundIndicator) indicator, v);
    // }
    // }
    //
    // }
    //
    // }

    @SuppressWarnings("unchecked")
    public void evalExprFields(Indicator indicator, Object value) {
        // logger.debug("eval expr fields of {}", indicator.name);
        if (indicator instanceof ListIndicator) {
            ListIndicator li = (ListIndicator) indicator;
            evalListFields(li, (List<Map<String, Object>>) value);
            if (li.getFilterExpression() != null) {
                filterList(li.getFilterExpression(), (List<Map<String, Object>>) value);
            }
        } else if (indicator instanceof CompoundIndicator) {
            evalCompoundFields((CompoundIndicator) indicator, (Map<String, Object>) value);
        }
    }

    public void evalCompoundFields(CompoundIndicator indicator, Map<String, Object> value) {

        indicator.getFieldsCollection().stream().filter(fld -> {
            if (!(fld instanceof ExprField))
                return false;
            ExprField ef = (ExprField) fld;

            if (ef.useFormulaNoValueOnly && value.get(ef.getName()) != null) {
                return false;
            }
            return true;
        }).forEach(fld -> {
            // logger.debug("eval field {}",fld.getName());
            evalComoundField(indicator, (ExprField) fld, new HashSet<ExprField>(indicator.getFieldsCount()), value);
        });

    }

    /**
     * evaluate expression field and it's references 
     * @param indicator
     * @param field
     * @param evaluated
     * @param value
     * @return
     */
    public void evalComoundField(CompoundIndicator indicator, ExprField field, Set<ExprField> evaluated,
        Map<String, Object> value) {

        // already has a value and useFormulaNoValueOnly means no eval on this
        if (field.useFormulaNoValueOnly && value.get(field.name) != null) {
            evaluated.add(field);
            return;
        }

        if (field.isDisabled()) {
            logger.error("{}.{} disabled for formula evaluation.", indicator.name, field.getName());
            evaluated.add(field);
            return;

        }
        Set<String> referred = field.getReferredNames();

        // evaluate referenced exprs first
        if (referred != null) {
            referred.stream().map(name -> indicator.getField(name)).filter(ref -> {
                return ref != null && ref != field && ref instanceof ExprField && !evaluated.contains(ref);
            }).forEach(ref -> evalComoundField(indicator, (ExprField) ref, evaluated, value));
        }

        // Object r = evalExpr(value, ef.getFormula());
        if (field.getCompiled() != null) {
            try {
                Object r = evalCompiledExpr(value, field.getCompiled());
                value.put(field.name, r);
            } catch (Exception e) {
                logger.error("error evaluating expr field {}.{}\n {}", indicator.name, field.name, e);
            } finally {
                evaluated.add(field);
            }
        }

    }

    public void setDepo(NEIndicatorDepo depo) {
        this.depo = depo;
    }

    public abstract Object evalExpr(Map<String, Object> currentItem, String expr);

    public abstract Object evalCompiledExpr(Map<String, Object> currentItem, Object compiled);

    public abstract void compileExprItem(IExprItem item);

    private static ThreadLocal<ExprEvaluator> evaluatorForThead = new ThreadLocal<ExprEvaluator>() {
        @Override
        protected ExprEvaluator initialValue() {
            return new ExprEvaluatorMVEL();
        }
    };

    /**
     * get evaluator for this thread
     * @return
     */
    public static ExprEvaluator getEvaluator() {
        return evaluatorForThead.get();
    }
}
